﻿#region License

// PentaBiz - Sustainable Software Development Framework Copyright (C) 2013 Zoltán Csizmazia
// 
// This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 

#endregion

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.Serialization;
using PentaBiz.Collections;

namespace PentaBiz.Cloud.CloudDb.EntityModel.MetaModel
{
    [DataContract(IsReference = true)]
    //[DataServiceKey("Id")]
    public class MetaApplication : MetaEntity<MetaApplication>
    {
        private MetaChangeSet _workingMetaChangeSet;
        private MetaVersion _workingMetaVersion;

        [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
        public MetaApplication()
        {
            //NameSpace = "MetaApp_" + Id.ToString().Replace("-", "_");
            MetaDataTables = new List<MetaDataTable>();
            MetaSecurityRoles = new List<MetaSecurityRole>();
            MetaSecurityUsers = new List<MetaSecurityUser>();
            MetaTasks = new List<MetaTask>();
            MetaServiceThrottling = new MetaServiceThrottling();
            MetaChangeSets = new List<MetaChangeSet>();
        }

        ///// <summary>
        ///// Gets or sets the name space.
        ///// </summary>
        ///// <value>
        ///// The name space.
        ///// </value>
        //public string NameSpace { get; set; }


        /// <summary>
        ///     Gets or sets the name.
        /// </summary>
        /// <value>
        ///     The name.
        /// </value>
        [DataMember]
        [Required]
        [ValidIdentifierValidation]
        public string Name { get; set; }


        /// <summary>
        ///     Gets or sets the description.
        /// </summary>
        /// <value>
        ///     The description.
        /// </value>
        [DataMember]
        public string Description { get; set; }


        /// <summary>
        ///     Gets or sets the meta change sets.
        /// </summary>
        /// <value>
        ///     The meta change sets.
        /// </value>
        [DataMember]
        public virtual ICollection<MetaChangeSet> MetaChangeSets { get; set; }

        [DataMember]
        public virtual MetaVersion MetaVersion { get; set; }

        [DataMember]
        public virtual ICollection<MetaDataTable> MetaDataTables { get; set; }

        [DataMember]
        public virtual ICollection<MetaSecurityRole> MetaSecurityRoles { get; set; }

        [DataMember]
        public virtual ICollection<MetaSecurityUser> MetaSecurityUsers { get; set; }

        [DataMember]
        public virtual ICollection<MetaTask> MetaTasks { get; set; }

        /// <summary>
        ///     Gets or sets a value indicating whether this instance is deployable.
        /// </summary>
        /// <value>
        ///     <c>true</c> if this instance is deployable; otherwise, <c>false</c>.
        /// </value>
        public bool IsDeployable { get; set; }

        /// <summary>
        ///     Gets or sets a value indicating whether this instance is sample.
        /// </summary>
        /// <value>
        ///     <c>true</c> if this instance is sample; otherwise, <c>false</c>.
        /// </value>
        public bool IsSample { get; set; }

        public virtual MetaServiceThrottling MetaServiceThrottling { get; set; }

        public MetaChangeSet SelectWorkingMetaChangeSet(MetaDbContext dbContext)
        {
            return (from ms in dbContext.MetaChangeSets
                    where ms.MetaApplication.Id == Id && ms.MetaApplication.IsDeleted == false
                    orderby ms.MetaVersion.MajorVersion descending,
                        ms.MetaVersion.MinorVersion descending,
                        ms.MetaVersion.BuildVersion descending,
                        ms.MetaVersion.RevisionNumber descending
                    select ms).First();
        }

        /// <summary>
        ///     Gets the working meta change set.
        /// </summary>
        /// <returns></returns>
        public MetaChangeSet GetWorkingMetaChangeSet()
        {
            if (_workingMetaChangeSet != null)
                return _workingMetaChangeSet;

            IOrderedEnumerable<MetaChangeSet> query = from m in MetaChangeSets
                                                      orderby
                                                          m.MetaVersion.MajorVersion descending,
                                                          m.MetaVersion.MinorVersion descending,
                                                          m.MetaVersion.BuildVersion descending,
                                                          m.MetaVersion.RevisionNumber descending
                                                      select m;

            _workingMetaChangeSet = query.FirstOrDefault();
            if (_workingMetaChangeSet != null)
                _workingMetaVersion = new MetaVersion(_workingMetaChangeSet.MetaVersion);

            return _workingMetaChangeSet;
        }

        /// <summary>
        ///     Ensures the major version incrementation.
        /// </summary>
        /// <returns></returns>
        public MetaChangeSet EnsureMajorVersionIncrementation()
        {
            if (MetaVersion == null)
                MetaVersion = new MetaVersion();

            if (_workingMetaVersion == null)
                _workingMetaVersion = new MetaVersion();

            MetaVersion nextApiVersion = MetaVersion.IncrementMajorVersion();

            if (nextApiVersion > _workingMetaVersion)
            {
                _workingMetaVersion = new MetaVersion(nextApiVersion);
                MetaChangeSets.Add(new MetaChangeSet
                    {
                        MetaApplication = this,
                        MetaVersion = new MetaVersion(_workingMetaVersion),
                        CreatedAt = DateTime.UtcNow,
                        ModifiedAt = DateTime.UtcNow,
                    });
            }

            IEnumerable<MetaChangeSet> metaChangesetQuery = from m in MetaChangeSets
                                                            where m.MetaVersion.Equals(_workingMetaVersion)
                                                            select m;

            MetaChangeSet metaChangeSet = metaChangesetQuery.First();
            _workingMetaChangeSet = metaChangeSet;
            return metaChangeSet;
        }

        /// <summary>
        ///     Ensures the minor version incrementation.
        /// </summary>
        /// <returns></returns>
        public MetaChangeSet EnsureMinorVersionIncrementation()
        {
            if (MetaVersion == null)
                MetaVersion = new MetaVersion();

            if (_workingMetaVersion == null)
                _workingMetaVersion = new MetaVersion();

            MetaVersion nextApiVersion = MetaVersion.IncrementMinorVersion();

            if (nextApiVersion > _workingMetaVersion)
            {
                _workingMetaVersion = new MetaVersion(nextApiVersion);
                MetaChangeSets.Add(new MetaChangeSet
                    {
                        MetaApplication = this,
                        MetaVersion = new MetaVersion(_workingMetaVersion),
                        CreatedAt = DateTime.UtcNow,
                        ModifiedAt = DateTime.UtcNow,
                    });
            }

            IEnumerable<MetaChangeSet> metaChangesetQuery = from m in MetaChangeSets
                                                            where m.MetaVersion.Equals(_workingMetaVersion)
                                                            select m;

            MetaChangeSet metaChangeSet = metaChangesetQuery.First();
            _workingMetaChangeSet = metaChangeSet;
            return metaChangeSet;
        }

        /// <summary>
        ///     Ensures the maintenance version incrementation.
        /// </summary>
        /// <returns></returns>
        public MetaChangeSet EnsureMaintenanceVersionIncrementation()
        {
            if (MetaVersion == null)
                MetaVersion = new MetaVersion();

            if (_workingMetaVersion == null)
                _workingMetaVersion = new MetaVersion();

            MetaVersion nextApiVersion = MetaVersion.IncrementBuildNumber();

            if (nextApiVersion > _workingMetaVersion)
            {
                _workingMetaVersion = new MetaVersion(nextApiVersion);
                MetaChangeSets.Add(new MetaChangeSet
                    {
                        MetaApplication = this,
                        MetaVersion = new MetaVersion(_workingMetaVersion),
                        CreatedAt = DateTime.UtcNow,
                        ModifiedAt = DateTime.UtcNow,
                    });
            }

            IEnumerable<MetaChangeSet> metaChangesetQuery = from m in MetaChangeSets
                                                            where m.MetaVersion.Equals(_workingMetaVersion)
                                                            select m;

            MetaChangeSet metaChangeSet = metaChangesetQuery.First();
            _workingMetaChangeSet = metaChangeSet;
            return metaChangeSet;
        }

        /// <summary>
        ///     Ensures the build version incrementation.
        /// </summary>
        /// <returns></returns>
        public MetaChangeSet EnsureBuildVersionIncrementation()
        {
            if (MetaVersion == null)
                MetaVersion = new MetaVersion();

            if (_workingMetaVersion == null)
                _workingMetaVersion = new MetaVersion();

            MetaVersion nextApiVersion = MetaVersion.IncrementRevisionNumber();

            if (nextApiVersion > _workingMetaVersion)
            {
                _workingMetaVersion = new MetaVersion(nextApiVersion);
                MetaChangeSets.Add(new MetaChangeSet
                    {
                        MetaApplication = this,
                        MetaVersion = new MetaVersion(_workingMetaVersion),
                        CreatedAt = DateTime.UtcNow,
                        ModifiedAt = DateTime.UtcNow,
                    });
            }

            IEnumerable<MetaChangeSet> metaChangesetQuery = from m in MetaChangeSets
                                                            where m.MetaVersion.Equals(_workingMetaVersion)
                                                            select m;

            MetaChangeSet metaChangeSet = metaChangesetQuery.First();
            _workingMetaChangeSet = metaChangeSet;
            return metaChangeSet;
        }


        /// <summary>
        ///     Adds the new meta data table.
        /// </summary>
        /// <param name="initializer">The initializer.</param>
        /// <returns></returns>
        public MetaDataTable AddNewMetaDataTable(Action<MetaDataTable> initializer)
        {
            var mt = new MetaDataTable
                {
                    MetaApplication = this,
                };

            MetaDataTables.Add(mt);


            if (initializer != null)
                initializer(mt);

            return mt;
        }


        /// <summary>
        ///     Adds the new meta task.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <returns></returns>
        public MetaTask AddNewMetaTask(Action<MetaTask> action)
        {
            var mt = new MetaTask
                {
                    MetaApplication = this,
                };

            MetaTasks.Add(mt);

            if (action != null)
                action(mt);

            return mt;
        }

        /// <summary>
        ///     Adds the new meta security role.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <returns></returns>
        public MetaSecurityRole AddNewMetaSecurityRole(Action<MetaSecurityRole> action)
        {
            var mt = new MetaSecurityRole
                {
                    MetaApplication = this,
                };

            MetaSecurityRoles.Add(mt);

            if (action != null)
                action(mt);

            return mt;
        }

        /// <summary>
        ///     Adds the new meta security user.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <returns></returns>
        public MetaSecurityUser AddNewMetaSecurityUser(Action<MetaSecurityUser> action)
        {
            var mt = new MetaSecurityUser
                {
                    MetaApplication = this,
                };

            MetaSecurityUsers.Add(mt);

            if (action != null)
                action(mt);

            return mt;
        }


        internal override TriggerResult OnAdding(IScopedDbContext scopedDbContext)
        {
            MetaChangeSet metaChangeset = EnsureMajorVersionIncrementation();
            metaChangeset.AddNewMetaChangeItem(a =>
                {
                    a.ChangeType = MetaChangeTypeEnum.Added;
                    a.ChangeText =
                        string.Format("MetaApplication '{0}' has been added", Name);
                    a.OnAdding(scopedDbContext);
                });

            MetaVersion = new MetaVersion(metaChangeset.MetaVersion);

            return TriggerResult.EntityAdded.Combine(base.OnAdding(scopedDbContext));
        }

        internal override TriggerResult OnModifying(IScopedDbContext scopedDbContext)
        {
            MetaChangeSet metaChangeset = EnsureMajorVersionIncrementation();
            metaChangeset.AddNewMetaChangeItem(a =>
                {
                    a.ChangeType = MetaChangeTypeEnum.Modified;
                    a.ChangeText =
                        string.Format("MetaApplication '{0}' has been modified", Name);
                    a.OnAdding(scopedDbContext);
                });

            MetaVersion = new MetaVersion(metaChangeset.MetaVersion);

            return TriggerResult.EntityAdded.Combine(base.OnModifying(scopedDbContext));
        }

        internal override TriggerResult OnDeleting(IScopedDbContext scopedDbContext)
        {
            MetaChangeSet metaChangeset = EnsureMajorVersionIncrementation();
            metaChangeset.AddNewMetaChangeItem(a =>
                {
                    a.ChangeType = MetaChangeTypeEnum.Deleted;
                    a.ChangeText =
                        string.Format("MetaApplication '{0}' has been removed", Name);
                    a.OnAdding(scopedDbContext);
                });

            return TriggerResult.EntityAdded.Combine(base.OnDeleting(scopedDbContext));
        }


        public static MetaApplication CreateSampleApplication(string name,
                                                              int numberOfTables = 20,
                                                              int numberOfColumns = 10,
                                                              int numberOfRoles = 10,
                                                              int numberOfUsers = 100,
                                                              int numberOfTasks = 500
            )
        {
            var rnd = new Random();

            var ma = new MetaApplication
                {
                    Name = name,
                    Description = "Sample Customer application",
                    IsDeployable = true,
                    IsSample = true
                };

            var types = new[]
                {
                    typeof (string), typeof (decimal), typeof (int), typeof (DateTime), typeof (long),
                    typeof (TimeSpan)
                };

            for (int i = 0; i < numberOfTables; i++)
            {
                MetaDataTable tbl = ma.AddNewMetaDataTable(a => { a.SetName("Table" + i); });

                for (int y = 0; y < numberOfColumns; y++)
                {
                    tbl.AddNewMetaDataField(a =>
                        {
                            a.SetName("Column" + y);
                            a.DbType = types.TakeRandom();
                        });
                }


                int referencedTablesCount = rnd.Next(Math.Min(3, i));


                for (int j = 0; j < referencedTablesCount; j++)
                {
                    int referencedTableIndex = rnd.Next(3);
                    MetaDataTable referencedTable = ma.MetaDataTables.Skip(referencedTableIndex).FirstOrDefault();
                    //add only reference not added already
                    while (tbl.MetaDataTableReferenceFields.Any(r => r.ReferencedMetaTable == referencedTable))
                    {
                        referencedTableIndex = rnd.Next(3);
                        referencedTable = ma.MetaDataTables.Skip(referencedTableIndex).FirstOrDefault();
                    }

                    Debug.Assert(referencedTable != null, "referencedTable != null");

                    tbl.AddNewMetaDataReferenceField(referencedTable,
                                                     a =>
                                                         {
                                                             string fieldName = string.Format("{0}_{1}_{2}", ma.Name,
                                                                                              referencedTable.Name, j);
                                                             a.SetName(fieldName);
                                                         });
                }
            }

            for (int i = 0; i < numberOfRoles; i++)
            {
                ma.AddNewMetaSecurityRole(a => { a.RoleName = "Role" + i; });
            }

            for (int i = 0; i < numberOfUsers; i++)
            {
                MetaSecurityUser usr = ma.AddNewMetaSecurityUser(a => { a.UserName = "User" + i; });
            }
            for (int i = 0; i < numberOfTasks; i++)
            {
                ma.AddNewMetaTask(a =>
                    {
                        a.SetName("Task" + i);
                        int numberOfHandles = rnd.Next(3);
                        for (int j = 0; j < numberOfHandles; j++)
                        {
                            int idx = rnd.Next(0, ma.MetaDataTables.Count);
                            MetaDataTable handledTable = ma.MetaDataTables.Skip(idx).FirstOrDefault();

                            if (a.HandledDataTables.Any(hdt => hdt.HandledMetaDataTable == handledTable))
                                continue;

                            a.AddNewMetaTaskHandledDataTable(handledTable, aa => { });
                        }
                        int numberOfProvides = rnd.Next(1);
                        for (int j = 0; j < numberOfProvides; j++)
                        {
                            int idx = rnd.Next(0, ma.MetaDataTables.Count);
                            MetaDataTable providedTable = ma.MetaDataTables.Skip(idx).FirstOrDefault();

                            if (a.ProvidedDataTables.Any(hdt => hdt.ProvidedMetaDataTable == providedTable))
                                continue;

                            a.AddNewMetaTaskProvidedDataTable(providedTable, aa => { });
                        }
                    });
            }


            return ma;
        }
    }
}